vue2 的响应式原理:在 vue 组件初始化时,先遍历 data 中的数据,对 data 中每个为对象类型的数据做 observe 处理,然后通过 Object.defineProperty 对 getter 和 setter 做拦截处理。接着在组件进行挂载时,会实例化一个渲染 Watcher,同时执行 render 函数生成组件 vnode,生成 vnode 过程如果有获取数据则会触发 getter 逻辑,进而该 data 数据就会收集到对应依赖。在该 data 数据更新后,会触发 setter 逻辑,通知所有依赖执行 patch 逻辑。
在响应式数据处理方面,相较于 vue2,vue3 的不同点有:
- vue3 不会在组件初始化的时候就遍历 data 数据进行数据拦截处理,而是使用 Proxy 替代 Object.defineProperty,对整个对象数据进行代理,节省初始化时的开销。
- vue2 使用的是发布订阅者配合 Object.defineProperty 实现响应式,而 vue3 则是用 Proxy 配合事件调度实现(effect + track + trigger)。
vue2 对数据做拦截处理在源码内部处理,而 vue3 则是将处理成响应式数据的接口暴露给用户。有:reactive、ref、readonly、shallowReactive、shadowReadonly。
以下面例子进行响应式源码分析:
<template>
<span>{{ state.msg }}</span>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
msg: 'text'
})
return state
}
}
</script>
# reactive 函数
源码分析路径:@vue/reactivity/dist/reactivity.esm-bundler.js
function reactive(target) {
// 通过判断 __v_isReadonly 属性是否为 true,是的话不做处理
if (isReadonly(target)) {
return target;
}
// 调用 createReactiveObject 函数,返回经过 proxy 代理的对象
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
// 非对象类型,不做处理
if (!isObject(target)) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// 当前对象本身就是一个被 proxy 代理过的对象,不做处理
if (target["__v_raw" /* RAW */] &&
!(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
return target;
}
// 通过缓存判断当前对象是否被 proxy 处理过,是的话返回缓存的代理对象
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 无效对象不做处理
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
// Proxy 处理,通过 targetType 设置 Proxy 的 handler
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy); // 缓存代理对象
return proxy; // 返回代理对象
}
经分析,reactive 函数最终调用的是 createReactiveObject 函数。该函数的函数入参有:
- target:被代理对象。
- isReadonly:是否只读。
- baseHandlers:target 为 Object 或者 Array 类型的 Proxy handler。
- collectionHandlers:target 为 Set、Map、WeakSet、WeakMap 类型的 Proxy handler。
- proxyMap:Proxy 代理过的对象数据缓存。
createReactiveObject 函数执行逻辑如下:
- 如果 target 为非对象类型,不做处理,返回原 target 数据。
- 判断当前对象本身是否是被 proxy 代理过的对象,是的话不做处理,返回原 target 数据。
- 通过 proxyMap 缓存判断当前对象是否被 proxy 处理过,是的话返回缓存的代理对象。
- 判断是否为无效对象,是的话不做处理,返回原 target 数据。
- 通过不同的 targetType 获取对应的 Proxy handler,进而进行 Proxy 处理。在例子中,由于 target 为 Object 类型,所以对应的 Proxy handler 为 baseHandlers。
- 最后缓存并返回代理对象。
# baseHandlers
经分析,baseHandlers 其实就是 mutableHandlers。
const mutableHandlers = {
get: createGetter(),
set: createSetter(),
deleteProperty,
has,
ownKeys
};
# createGetter 函数
createGetter 函数源码:
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// 获取特殊 key 值处理
if (key === "__v_isReactive" /* IS_REACTIVE */) {
return !isReadonly;
}
else if (key === "__v_isReadonly" /* IS_READONLY */) {
return isReadonly;
}
else if (key === "__v_isShallow" /* IS_SHALLOW */) {
return shallow;
}
else if (key === "__v_raw" /* RAW */ &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap).get(target)) {
return target;
}
// 数组类型处理且获取 key 值为数组原型上的一些方法,对该方法做处理
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
// 获取对应值,对值做不同类型处理
const res = Reflect.get(target, key, receiver);
// key 为 Symbol 类型,不做处理,返回 key 对应的值
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res;
}
// 不是只读对象,则调用 track 函数对 target 中的 key 值做依赖处理
if (!isReadonly) {
track(target, "get" /* GET */, key);
}
// shallowReactive 处理,不对子对象做响应式处理
if (shallow) {
return res;
}
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
return shouldUnwrap ? res.value : res;
}
if (isObject(res)) {
// 非只读对象,对子对象也做响应也做响应式处理
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
函数执行逻辑:
- 先对特殊的 key 做特殊处理。如果获取的 key 是特殊的 key,则做对应特殊处理。
- 接着判断 target 数据是否为数组类型,是的话且获取的 key 为数组原型上的一些方法名,则对该方法做处理。
- 然后判断 key 是否为 Symbol 类型,是的话不做处理,直接返回 key 对应的值。
- 如果非只读对象,则调用 track 函数对 target 中的 key 值做依赖处理。
- 接着判断是否为 shallowReactive 浅处理
- 是的话则不对子对象做响应式递归处理。
- 不是的话且 key 对应的值为 Object 类型,则递归调用 reactive 对子对象做响应式处理。
- 返回结果。
# track 函数
track 函数的作用是:有激活的 effect 时,对该 effect 进行收集。
function track(target, type, key) {
// 全局变量 shouldTrack 和 activeEffect 都存在时,执行依赖收集
if (shouldTrack && activeEffect) {
// 判断当前的 target 数据对象是否存在于缓存中,没有缓存过,则缓存当前的 target 数据到 targetMap 中
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 获取当前 key 的依赖集合,如果没有依赖,则创建一个
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = createDep()));
}
const eventInfo = (process.env.NODE_ENV !== 'production')
? { effect: activeEffect, target, type, key }
: undefined;
trackEffects(dep, eventInfo); // 调用 trackEffects 函数
}
}
function trackEffects(dep, debuggerEventExtraInfo) {
let shouldTrack = false;
// effect track 的深度判断,最大为 30
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit; // set newly tracked
shouldTrack = !wasTracked(dep);
}
}
else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect);
}
if (shouldTrack) {
dep.add(activeEffect); // 将当前 target 对象的 key 的依赖者 activeEffect 添加到 dep 依赖者集合中
activeEffect.deps.push(dep); // 将当前的 dep 保存到 activeEffect.deps 中
if ((process.env.NODE_ENV !== 'production') && activeEffect.onTrack) {
activeEffect.onTrack(Object.assign({ effect: activeEffect }, debuggerEventExtraInfo));
}
}
}
上面源码执行逻辑为:
- 如果当前没有激活的 effect,则不进行依赖收集。
- 有激活的 effect 情况,会通过 target 对象在 targetMap 缓存中获取对应的依赖集合 depsMap,如果没有则创建一个 depsMap 并缓存在 targetMap 中。
- 接着会通过 depsMap 获取当前 target 对象的属性 key 的依赖集合,没有则创建一个。
- 然后对 effect track 的深度判断,最大为 30。
- 最后将当前激活的 effect 添加到属性 key 的依赖集合 dep 中,并将 dep 保存到当前激活的 effect 的 deps 中。
# createSetter 函数
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
let oldValue = target[key];
// 只读类型数据不做处理
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false;
}
if (!shallow && !isReadonly(value)) {
if (!isShallow(value)) {
value = toRaw(value);
oldValue = toRaw(oldValue);
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value);
}
else if (hasChanged(value, oldValue)) {
trigger(target, "set" /* SET */, key, value, oldValue);
}
}
return result;
};
}
经分析,可以看到函数最后主要执行 trigger 函数。
# trigger 函数
function trigger(target, type, key, newValue, oldValue, oldTarget) {
const depsMap = targetMap.get(target);
// 当前 target 对象没有被追踪处理,直接返回
if (!depsMap) {
return;
}
let deps = [];
if (type === "clear" /* CLEAR */) {
// 当前的所有依赖正在被清理,将触发所有的 effect
deps = [...depsMap.values()];
}
else if (key === 'length' && isArray(target)) {
// 数组类型且设置 key 值为 length(修改数组长度),遍历数组搜集搜有的依赖
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newValue) {
deps.push(dep);
}
});
}
else {
// SET ADD DELETE 类型
if (key !== void 0) {
// 获取当前 key 的 dep(即:key 的所有 effect Set 集合)
deps.push(depsMap.get(key));
}
// 迭代类型的 key 处理
switch (type) {
case "add" /* ADD */:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'));
}
break;
case "delete" /* DELETE */:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
case "set" /* SET */:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY));
}
break;
}
}
const eventInfo = (process.env.NODE_ENV !== 'production')
? { target, type, key, newValue, oldValue, oldTarget }
: undefined;
if (deps.length === 1) {
if (deps[0]) {
if ((process.env.NODE_ENV !== 'production')) {
triggerEffects(deps[0], eventInfo);
}
else {
triggerEffects(deps[0]);
}
}
}
else {
const effects = [];
for (const dep of deps) {
if (dep) {
effects.push(...dep);
}
}
if ((process.env.NODE_ENV !== 'production')) {
triggerEffects(createDep(effects), eventInfo);
}
else {
triggerEffects(createDep(effects));
}
}
}
上面函数的执行逻辑:先对 target 对象的 depsMap 做判空处理;接着根据传入的 type 类型获取当前的 target 对象的属性 key 在 depsMap 中的 effect Set 集合列表,并将该集合列表存放到变量 deps 中,最后调用 triggerEffects 函数。
# triggerEffects 函数
function triggerEffects(dep, debuggerEventExtraInfo) {
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
if ((process.env.NODE_ENV !== 'production') && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo));
}
if (effect.scheduler) {
effect.scheduler();
}
else {
effect.run();
}
}
}
}
函数执行逻辑:由于 dep 为 Set 数据结构,如果 dep 非数组类型,会先将 dep 转为数组。遍历 dep 列表,触发执行 effect。执行 effect 有三种方式:
- onTrigger:主要是开发环境 debug 使用。
- scheduler:通过调度程序执行 effect.run。
- run:直接调用 effect.run。
scheduler 调度程序的定义在createAppAPI -> mount -> render -> patch -> processComponent -> mountComponent -> setupRenderEffect
中,在 setupRenderEffect 函数中,会实例化一个 ReactiveEffect 对象。scheduler 的定义源码如下:
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update), // scheduler
instance.scope));
const update = (instance.update = effect.run.bind(effect));
分析可得,queueJob 的函数参数 instance.update 其实就是 effect.run 函数。queueJob 函数源码比较复杂,后面再单独拎出来分析。其实,queueJob 最后执行的也是 effect.run 函数。effect.run 函数会执行实例化 ReactiveEffect 对象时传入的回调 componentUpdateFn 函数,该函数会执行 patch 逻辑更新视图数据。
接下来看 effect 实现逻辑。
# effect 实现逻辑
在 vue 组件执行 mount 时,会先通过 setupComponent 函数实现组件实例初始化,对 data 数据进行响应式处理,即:通过 reactive 等函数处理的数据;然后就通过 setupRenderEffect 函数创建一个渲染 effect。
mount 函数主要源码如下:
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = Object.assign({}, rootComponent);
}
const app = (context.app = {
_uid: uid++,
_component: rootComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
mount(rootContainer, isHydrate, isSVG) {
if (!isMounted) {
const vnode = createVNode(rootComponent, rootProps);
// ...
// 执行 render 函数
render(vnode, rootContainer, isSVG);
// ...
return getExposeProxy(vnode.component) || vnode.component.proxy;
}
// ...
}
})
return app;
};
}
render 函数主要源码如下:
function baseCreateRenderer(options, createHydrationFns) {
const render = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true);
}
}
else {
// 执行 patch 函数
patch(container._vnode || null, vnode, container, null, null, null, isSVG);
}
flushPostFlushCbs();
container._vnode = vnode;
};
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
}
经分析,render 函数执行的是 patch 函数。
由于是 vue 组件的挂载,所以 patch 函数内部主要源码为:
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = (process.env.NODE_ENV !== 'production') && isHmrUpdating ? false : !!n2.dynamicChildren) => {
// ...
const { type, ref, shapeFlag } = n2;
switch (type) {
// ...
default:
if (shapeFlag & 1 /* ELEMENT */) {
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
else if (shapeFlag & 6 /* COMPONENT */) {
// 执行组件类型的 patch
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
// ...
}
// ...
};
processComponent 函数主要源码:
const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
n2.slotScopeIds = slotScopeIds;
if (n1 == null) {
if (n2.shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {
parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized);
}
else {
mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
}
else {
updateComponent(n1, n2, optimized);
}
};
上面源码比较简单,挂载执行的是 mountComponent 函数。
mountComponent 函数主要执行:
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
// ...
setupComponent(instance); // 实现组件实例初始化,对 data 数据进行响应式处理,即:通过 reactive 等函数处理的数据
// ...
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized); // 创建一个渲染 effect
// ...
}
# setupRenderEffect 函数
函数主要源码如下:
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
const componentUpdateFn = () => {
// 触发 effect.run 执行的回调函数
if (!instance.isMounted) {
// ...
// 生成 vnode
const subTree = (instance.subTree = renderComponentRoot(instance));
// 主要执行 patch 逻辑
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
// ...
}
// ...
}
// 创建一个 effect
const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () => queueJob(instance.update), instance.scope));
// instance.update 实际上就是 effect.run 函数
const update = (instance.update = effect.run.bind(effect));
update.id = instance.uid;
// ...
update();
}
setupRenderEffect 函数先创建一个 effect,然后将 effect.run 函数赋值给 instant.update,接着手动执行一次 update 函数;update 函数内部会先生成 vnode,生成 vnode 过程会如果有获取 data 数据则会触发 Proxy getter 逻辑,实现对应数据的依赖搜集,生成 vnode 之后会执行 patch 更新视图。
# ReactiveEffect
源码如下:
class ReactiveEffect {
constructor(fn, scheduler = null, scope) {
this.fn = fn; // 回调函数,也就是上面的 componentUpdateFn 函数
this.scheduler = scheduler;
this.active = true;
this.deps = [];
this.parent = undefined;
recordEffectScope(this, scope);
}
run() {
if (!this.active) {
return this.fn();
}
let parent = activeEffect;
let lastShouldTrack = shouldTrack;
while (parent) {
if (parent === this) {
return;
}
parent = parent.parent;
}
try {
this.parent = activeEffect;
activeEffect = this;
shouldTrack = true;
trackOpBit = 1 << ++effectTrackDepth;
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this);
}
else {
cleanupEffect(this);
}
return this.fn(); // 执行回调函数
}
finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this);
}
trackOpBit = 1 << --effectTrackDepth;
activeEffect = this.parent;
shouldTrack = lastShouldTrack;
this.parent = undefined;
if (this.deferStop) {
this.stop();
}
}
}
stop() {
if (activeEffect === this) {
this.deferStop = true;
}
else if (this.active) {
cleanupEffect(this);
if (this.onStop) {
this.onStop();
}
this.active = false;
}
}
}
可见,effect.run 函数最后执行的是回调函数 componentUpdateFn。
# 总结
vue3 的响应式逻辑是在组件执行 mount 时,会先通过 setupComponent 函数初始化组件实例,实现 data 数据的 Proxy 代理,也就是实现 Proxy 的 setter、getter 等逻辑;接着通过 setupRenderEffect 创建一个渲染 effect,然后手动执行一次 effect.run 函数,effect.run 函数内部也就执行了传入的回调函数,该回调函数会先生成 vnode,生成 vnode 过程如果有获取 data 数据则会触发对应的 getter 逻辑,实现依赖收集(也就是收集 effect),生成完 vnode 之后,会执行 patch 逻辑更新视图。